﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ImageProcessingLib
{
    /// <summary>
    /// Класс матрицы для фильтрации изображения.
    /// </summary>
    public class Matrix
    {
        readonly float[] _data;
        readonly int _rows;
        readonly int _columns;

        public Matrix(int rows, int columns)
        {
            _rows = rows;
            _columns = columns;
            _data = new float[_rows * _columns];
        }

        public int Columns => _columns;
        public int Rows => _rows;

        public void SetItem(int x, int y, float v)
        {
            _data[y * _columns + x] = v;
        }

        /// <summary>
        /// Матричная свертка.
        /// </summary>
        /// <param name="a">Матрица.</param>
        /// <param name="b">Матрица.</param>
        /// <returns>Числовое значение.</returns>
        public static float operator *(Matrix a, Matrix b)
        {
            float vp = 0;
            float s = 0;

            for (int i = 0; i < a._data.Length; i++)
            {
                s += a._data[i];
                vp += a._data[i] * b._data[i];
            }

            return (Math.Abs(s) > 0.001f) ? vp / s : vp;
        }
    }

    /// <summary>
    /// Матричные пресеты.
    /// </summary>
    public class MatrixFactory
    {
        /// <summary>
        /// Размытие
        /// </summary>
        /// <param name="size">Величина размытия.</param>
        /// <returns>Матрица фильтра.</returns>
        public static Matrix Blur(int size = 1)
        {
            int n = 2 * size + 1;
            Matrix matrix = new Matrix(n, n);

            for (int x = 0; x < n; x++)
            {
                for (int y = 0; y < n; y++)
                {
                    matrix.SetItem(x, y, 1.0f);
                }
            }

            return matrix;
        }

        /// <summary>
        /// Фильтр выделения контуров изображения.
        /// </summary>
        /// <returns>Матрица фильтра.</returns>
        public static Matrix Contour()
        {
            const int cSize = 3;
            Matrix matrix = new Matrix(cSize, cSize);

            for (int x = 0; x < cSize; x++)
            {
                for (int y = 0; y < cSize; y++)
                {
                    if (x == 1 && y == 1)
                        matrix.SetItem(x, y, -8.0f);
                    else
                        matrix.SetItem(x, y, 1.0f);
                }
            }

            return matrix;
        }

        /// <summary>
        /// Придание рельефности изображению.
        /// </summary>
        /// <param name="angle">Угол подсветки в радианах.</param>
        /// <returns>Матрица фильтра.</returns>
        public static Matrix Relief(double angle)
        {
            const int cSize = 3;
            Matrix matrix = new Matrix(cSize, cSize);

            double dr = Math.PI / 4;
            double r = angle;

            matrix.SetItem(0, 0, Convert.ToSingle(Math.Cos(r + dr)));
            matrix.SetItem(0, 1, Convert.ToSingle(Math.Cos(r + 2 * dr)));
            matrix.SetItem(0, 2, Convert.ToSingle(Math.Cos(r + 3 * dr)));

            matrix.SetItem(1, 0, Convert.ToSingle(Math.Cos(r)));
            matrix.SetItem(1, 1, 1.0f);
            matrix.SetItem(1, 2, Convert.ToSingle(Math.Cos(r + 4 * dr)));

            matrix.SetItem(2, 0, Convert.ToSingle(Math.Cos(r - dr)));
            matrix.SetItem(2, 1, Convert.ToSingle(Math.Cos(r - 2 * dr)));
            matrix.SetItem(2, 2, Convert.ToSingle(Math.Cos(r - 3 * dr)));

            return matrix;
        }

        /// <summary>
        /// Улучшения четкости изображения.
        /// </summary>
        /// <returns>Матрица фильтра.</returns>
        public static Matrix Sharpen()
        {
            const int cSize = 3;
            Matrix matrix = new Matrix(cSize, cSize);

            matrix.SetItem(0, 0, 1);    matrix.SetItem(0, 1, 0);   matrix.SetItem(0, 2, 1);
            matrix.SetItem(1, 0, 0);    matrix.SetItem(1, 1, -8);  matrix.SetItem(1, 2, 0);
            matrix.SetItem(2, 0, 1);    matrix.SetItem(2, 1, 0);   matrix.SetItem(2, 2, 1);

            return matrix;
        }
    }
}
